#ifndef __CLIENT_GPS_jkjihhugyuyuttr5r6r56__
#define __CLIENT_GPS_jkjihhugyuyuttr5r6r56__

#include <string>
#include <sstream>
#include <iterator>
#include <map>

#include <hude/base/optional.hpp>
#include <hude/aio/context.hpp>
#include <hude/utils/serialport.hpp>

namespace ha
{
    /*********************************************************************
     * @brief gps类型
     */
    class gps_client_t
    {
    public:
        using aio_t   = hude::aio::aio_t<>;
        using sport_t = hude::utils::serialport_t;
        using infos_t = std::map< std::string, std::string >;

    public:
        gps_client_t( aio_t aio ) :
            aio_( aio ) {}

        bool init( const char* name, int speed );
        void deinit();

        hude::optional_t< std::string > info( const std::string& key ) const;
        hude::optional_t< std::string > operator[]( const std::string& key ) const;

    private:
        gps_client_t( gps_client_t&& ) = delete;
        gps_client_t( const gps_client_t& ) = delete;
        gps_client_t& operator=( const gps_client_t& ) = delete;

        void on_recv( uint32_t, hude::any_t& );
        void on_parse( hude::buffer_t& );

    private:
        aio_t   aio_;
        sport_t sport_;
        infos_t infos_;

    };

    /*********************************************************************
     * @brief ...
     */
    void gps_client_t::on_parse( hude::buffer_t& buffer )
    {
        enum : size_t { emSIZ = 255U };
        std::vector<char> line( emSIZ );

        std::istringstream iss( std::string{ buffer.begin(), buffer.end() } );

        while( iss.getline( &(line[0]), emSIZ, '\n' ) )
        {
            if( line.size() < 7U || line[0] != '$' || line[6] != ',' )
            {
                llog_war( "<plug-gps>", "invaild line: %s", &(line[0]) );

                continue;
            }

            // llog_dug( "<plug-gps>", "line: %s", &(line[0]) );

            std::string key( line.begin() + 1, line.begin() + 6 );

            auto itr = infos_.find( key );

            if( itr == infos_.end() )
            {
                llog_war( "<plug-gps>", "invaild key: %s by %s", key.c_str(), &(line[0]) );

                continue;
            }

            itr->second = std::string{ line.begin(), line.end() };
        }
    }

    /*********************************************************************
     * @brief ...
     */
    void gps_client_t::on_recv( uint32_t evts, hude::any_t& )
    {
        using evt_type_t = hude::aio::epoll_evt_t;

        if( 0U != ( evts & evt_type_t::emEPOLLIN ) )
        {
            do
            {
                enum : size_t { emSIZ = 1024U };
                hude::buffer_t buffer( emSIZ );

                auto ret = sport_.recv( &(buffer[0]), emSIZ );

                if( ret > 0 )
                {
                    buffer.resize( ret );

                    // llog_dug( "<plug-gps>", "\t\t <<< Recv buffer[ %s ]", hude::utils::to_hex( buffer ).c_str() );

                    on_parse( buffer );
                } else {
                    // if( ret == 0 ) llog_war( "<plug-gps>", "\t Event of aio: recv() == %d.", ret );

                    break;
                }
            } while ( false );
        }

        if( 0U != ( evts & evt_type_t::emEPOLLHUP ) )
        {
            llog_nte( "<plug-gps>", "\t Remote disconnected." );
        }

        if( 0U != ( evts & evt_type_t::emEPOLLERR ) )
        {
            llog_war( "<plug-gps>", "\t Error event." );

            sport_.close();
        }
    };

    /*********************************************************************
     * @brief gps类型
     */
    bool gps_client_t::init( const char* name, int speed )
    {
        assert( name != nullptr );
        assert( speed != 0 );

        infos_.emplace( "GNRMC", std::string{} );
        infos_.emplace( "GNVTG", std::string{} );
        infos_.emplace( "GNGGA", std::string{} );
        infos_.emplace( "GNGSA", std::string{} );
        infos_.emplace( "GPGSV", std::string{} );
        infos_.emplace( "GPGSV", std::string{} );
        infos_.emplace( "GPGSV", std::string{} );
        infos_.emplace( "GNGLL", std::string{} );

        if( ! sport_.open( name, false ) || !sport_.settings( speed ) )
        {
            return false;
        }

        auto cb = std::bind( &gps_client_t::on_recv, this, std::placeholders::_1, std::placeholders::_2 );

        if( 0 != aio_->evt_commit( sport_.native(), cb ) )
        {
            sport_.close();

            return false;
        }

        return true;
    }

    /*********************************************************************
     * @brief gps类型
     */
    void gps_client_t::deinit()
    {
        aio_->evt_remove( sport_.native() );

        sport_.close();
    }

    /*********************************************************************
     * @brief gps类型
     */
    hude::optional_t< std::string > gps_client_t::operator[]( const std::string& key ) const
    {
        return info( key );
    }

    /*********************************************************************
     * @brief gps类型
     */
    hude::optional_t< std::string > gps_client_t::info( const std::string& key ) const
    {
        auto itr = infos_.find( key );

        if( itr == infos_.end() || itr->second.empty() ) return hude::optional_null_t{};

        return itr->second;
    }

} // namespace ha

#endif //__CLIENT_GPS_jkjihhugyuyuttr5r6r56__